Skip to content

feat(harness-desktop): install and launch isolated agent CLIs - #845

Merged
ynadge merged 11 commits into
mainfrom
feat/desktop-managed-agent-runtime
Sep 6, 2026
Merged

feat(harness-desktop): install and launch isolated agent CLIs#845
ynadge merged 11 commits into
mainfrom
feat/desktop-managed-agent-runtime

Conversation

@ynadge

@ynadge ynadge commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Primary change type

  • Feature

Problem and motivation

Desktop must install coding providers independently of a user's Node installation and preserve their launch configuration in sessions, resume, and background Agent Map generation. JavaScript CLI entry points need the bundled interpreter, its arguments, and its environment on every launch path.

Summary and scope

  • Resolve and verify isolated Claude Code/Codex installations; support exact-version installation with bounded processes, cancellation, an owned POSIX supervisor, and Windows process-identity checks. Clear Electron runtime state before managed CLI code runs so its child commands inherit a normal environment.
  • Preserve managed interpreter arguments and environment through both providers' interactive and structured-inference paths, including Codex's private authentication broker and worker. Model output cannot supply launch arguments or write authority.
  • Integrate the Agent Map stack through fix(harness): reset legacy maps and initialize existing projects #844 and cover packaged private initialization in the desktop smoke suite.
  • Fix two release-verification issues: defer terminal disposal until xterm's queued viewport initialization finishes, and persist mock workspace selection across browser reloads. Update the first-project test to wait for the public running-session state before submitting input.

Related work

Merge after #844 and before #843. #843 adds startup update policy using these runtime primitives.

Validation

pnpm build / pnpm typecheck / pnpm lint — passed; existing lint warnings
pnpm --filter @sapiom/harness exec vitest run --maxWorkers=4 — 3,852 passed
harness performance suite — 10 passed
managed-provider and MCP focused tests — 15 passed
MockApi unit suite after browser fixes — 27 passed
focused generation/navigation/create-agent/composer browser suites — 55 passed
Desktop unit suite after runtime/process ownership fixes — 205 passed
macOS full installer packaging + packaged smoke — 15 passed, 1 Windows-only skip
git diff --check — passed

Real managed Claude Code 2.1.263 and Codex 0.153.4 both generated maps from copied previous-release profiles. The test verified create-only publication, restart persistence, later ordinary-session map edits, no public task broadcasts, and unchanged agent source files. Direct reset testing deleted five format-1 maps while preserving all eleven format-2 files byte for byte.

Node 20/22 CI passed before the final browser-only fix; the checks attached to this PR cover its final head. Local root tests encounter an unchanged agent-core permission-test incompatibility in the cloud VM. Two unchanged filesystem-watcher expectations differ on macOS; Linux CI covers them. Windows runtime behavior is covered by CI packaging and platform tests, not an interactive Windows device.

Tests and documentation

Added managed launch/worker protocol regressions and packaged smoke coverage; retained the browser restoration and no-page-error assertions that exposed the fixes. Adapter options, desktop setup, and Changesets document the release behavior.

Compatibility and release impact

  • Breaking or externally visible changes: additive adapter launch options and createCodexAdapter export. Existing callers retain defaults.
  • Changeset: harness minor for adapter API additions; desktop and terminal patch entries.

Security

  • No secrets, credentials, private data, or unsanitized logs included.
  • No suspected vulnerability publicly disclosed; the repository Security Policy applies.

AI assistance

  • Codex implemented the integration fixes, reviewed the diff, and ran the checks above.

Checklist

  • Read CONTRIBUTING.md; follows the requested contribution scope.
  • Runtime integration and release regressions are explained; tests and documentation included.
  • Relevant build, typecheck, lint, and tests run; limitations documented.
  • Changesets included; submitted changes reviewed.

@ynadge
ynadge marked this pull request as ready for review September 6, 2026 22:24
@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review — PR #845 (round 1)

No confidentiality findings in the changesets, JSDoc, or comments. @anthropic-ai/claude-code / @openai/codex are in the pinned public provider vocabulary.

1. @sapiom/harness changeset is the wrong level and describes work that isn't in the package

.changeset/managed-agent-runtime.md marks @sapiom/harness patch, but the diff adds public API to it:

  • export { createCodexAdapter } in src/index.ts — a new export from the package root;
  • binaryArgs / binaryEnv on ClaudeCodeAdapterOptions and CodexAdapterOptions — new public option fields;
  • a new optional binaryArgs parameter on the exported prepareCodexInferenceProfile / runCodexStructuredInference.

Repo precedent for exactly this is minor (.changeset/durable-delegation-state.md: "Publish … contracts … types" → minor). A consumer on ~0.14.0 gets the new export only by luck of range resolution.

The body is also inaccurate for the package it ships in. @sapiom/harness's published CHANGELOG will claim "isolated coding-agent installs", "Bound installer processes" and "verify packaged CLI launches" — all of that lives in harness-desktop (private: true, never published) — and "private Agent Map initialization", whose only harness change is a rewritten assertion in server/agent-map-mcp-wiring.test.ts. Split the desktop half into its own changeset and drop the Agent Map clause; a changelog line cannot be edited after publish. The house style for a foundation-only harness change is .changeset/bootstrap-coordinator.md ("Internal groundwork … No user-facing behavior changes in this release").

2. ELECTRON_RUN_AS_NODE=1 is exported into every command the agent runs

resolveAgentCommand (harness-desktop/src/main/managed-agent.ts:26) returns the host runtime's binaryEnv ({ELECTRON_RUN_AS_NODE: "1"} per smoke.ts:110), and the adapters now merge it into the session spec env (claude-code.ts:497,510,555, codex.ts:331,342). The PTY agent is the process that runs the user's own build and test commands, and env is inherited wholesale by everything it spawns.

Failure: a user working on an Electron project in Studio; the agent runs npx electron . or electron-builder, and Electron starts as bare Node instead of launching — silently, with no error to trace back. This is the same class the harness already defends against by stripping HOST_ESBUILD_PIN from every child env (packages/harness/CLAUDE.md §2). Existing uses of the flag (codex.ts launchTask, the shim-files.ts shims) scope it to a short-lived controlled subprocess; this is the first time it lands on an interactive session.

Fix: launch the managed JS entry through a wrapper that does delete process.env.ELECTRON_RUN_AS_NODE before requiring the CLI. Electron reads the flag only at process start, so deleting it post-start keeps the CLI running as Node while its children see a clean env.

3. POSIX process-group kill has no PID-reuse guard, unlike Windows

agent-update-process.ts:379process.kill(-child.pid, "SIGKILL"). stop() is reachable after the child exited: close only fires once the stdio pipes close, so a surviving grandchild holding the pipes keeps the promise pending until the timeout fires, by which point Node has reaped the child and the OS has freed the PID (exitedAt is set — the code records it but only consults it on Windows).

Failure: the freed PID is reused by a new group leader; kill(-pid) then SIGKILLs an entire unrelated process group belonging to the user. windowsUpdateTree defends against precisely this (identity.exitedAt !== undefined → return [], and the test at agent-update-process.test.ts:104 pins it). The POSIX branch — which the parentExits: true test case exercises — has no equivalent. Low probability on Linux, materially higher on macOS's smaller PID space. Cheapest fix: skip the group kill when exitedAt !== undefined, matching Windows.

4. managed-agent.ts has no unit tests

agent-update-process.ts got 132 lines of tests; resolveAgentCommand got none. Its only exercise is checkManagedAgent in smoke.ts, which runs a single happy path (.cjs bin, lib/node_modules layout) and only inside a packaged Electron build — so it never runs in pnpm test. Untested and load-bearing: the bin-escapes-the-package-dir rejection (managed-agent.ts:56), the Windows node_modules layout, and the native-binary branch that drops runtime.binaryEnv. These are cheap pure-function tests over a temp prefix.

Nit

  • The PR body publishes third-party model identifiers observed in a private account catalog ("GPT-6 Astra and GPT-5.6"). That detail proves nothing the version numbers don't; consider trimming it from a public PR.

Verdict

Request changes — the changeset level and body (1) must be fixed before this can publish, and (2) needs a decision before #843 wires it into real sessions.

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Review — PR #845 (round 2, follow-up)

Delta reviewed: 3cacd717..1aee7faf (2 commits, 9 files). No new confidentiality issues —
the three changesets name only Sapiom packages and the pinned public providers.

Round-1 findings: all four resolved

  1. Changeset level/body — fixed. @sapiom/harness is now minor and its body describes
    only what ships in that package (adapter interpreter args/env, createCodexAdapter, launch
    config across resume/structured inference); the desktop half moved to
    .changeset/desktop-managed-installs.md (patch) and the Agent Map clause is gone.
  2. ELECTRON_RUN_AS_NODE leaking into every agent-spawned command — fixed as suggested:
    managed-agent.ts:47 injects --import data:…delete process.env.ELECTRON_RUN_AS_NODE
    ahead of the entry, only when the runtime actually sets the flag, so the CLI still starts
    under Electron-as-Node while its children see a clean env. Covered by
    managed-agent.test.ts (asserts null in both the CLI and its grandchild) and by the
    smoke.ts:107 throw. --import needs Node ≥20.6; Electron 33 is 20.18, and the
    non-Electron path is unaffected.
  3. POSIX PID-reuse guard — fixed, and better than asked: an owned supervisor keeps the
    group leader alive until the installer's pipes close, and the group kill is now gated on
    exitedAt === undefined (agent-update-process.ts:202), matching Windows.
    agent-update-process-ownership.test.ts pins it.
  4. managed-agent.ts untested — fixed: both node_modules layouts, the native-binary
    branch, and the escaping-bin rejection now have tests that run in pnpm test.

The PR body no longer carries the third-party model identifiers from the round-1 nit.

Note (not blocking)

  • The deletion is unconditional for the CLI's descendants, so a managed JS CLI that
    re-executes process.execPath would now boot the Electron GUI rather than Node. Both
    pinned providers avoid it (Codex's bin execs its native binary; Claude Code's self-restart
    is off via DISABLE_AUTOUPDATER=1), but it's the assumption fix(harness-desktop): update Claude and Codex on startup #843 inherits.

I could not execute the desktop suites in this sandbox; the assessment is from the diff and
the assertions it adds.

Verdict: approve — merge per the PR's stated ordering (after #844, before #843).

@ynadge
ynadge merged commit 7ebf415 into main Sep 6, 2026
10 checks passed
@ynadge
ynadge deleted the feat/desktop-managed-agent-runtime branch September 6, 2026 23:08
@ynadge ynadge mentioned this pull request Sep 6, 2026
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant